/*
 *    Copyright (c) 2018-2025, sloth All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: sloth
 */
package cn.s1995.sloth.app.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.s1995.sloth.app.api.dto.AddUserRoleDTO;
import cn.s1995.sloth.app.api.dto.AppUserDTO;
import cn.s1995.sloth.app.api.dto.AppUserInfo;
import cn.s1995.sloth.app.api.dto.ForgetPasswordDTO;
import cn.s1995.sloth.app.api.entity.AppRole;
import cn.s1995.sloth.app.api.entity.AppUser;
import cn.s1995.sloth.app.api.entity.AppUserRole;
import cn.s1995.sloth.app.api.vo.AppUserExcelVO;
import cn.s1995.sloth.app.api.vo.AppUserVO;
import cn.s1995.sloth.app.mapper.AppUserMapper;
import cn.s1995.sloth.app.service.AppRoleService;
import cn.s1995.sloth.app.service.AppUserRoleService;
import cn.s1995.sloth.app.service.AppUserService;
import cn.s1995.sloth.app.util.PasswordUtil;
import cn.s1995.sloth.bizs.annotation.RedisLock;
import cn.s1995.sloth.bizs.constant.BizCst;
import cn.s1995.sloth.bizs.constant.RedisKeyCst;
import cn.s1995.sloth.bizs.constant.RegCst;
import cn.s1995.sloth.bizs.exception.BizException;
import cn.s1995.sloth.bizs.util.NameGenUtil;
import cn.s1995.sloth.bizs.util.ShareCodeUtil;
import cn.s1995.sloth.common.core.constant.CacheConstants;
import cn.s1995.sloth.common.core.constant.CommonConstants;
import cn.s1995.sloth.common.core.constant.enums.LoginTypeEnum;
import cn.s1995.sloth.common.core.constant.enums.hint.Hint;
import cn.s1995.sloth.common.core.exception.ErrorCodes;
import cn.s1995.sloth.common.core.util.MsgUtils;
import cn.s1995.sloth.common.core.util.R;
import cn.s1995.sloth.common.core.util.StringUtil;
import cn.s1995.sloth.common.core.util.WebUtils;
import cn.s1995.sloth.common.excel.vo.ErrorMessage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * app用户表
 *
 * @author aeizzz
 * @date 2022-12-07 09:52:03
 */
@Service
@AllArgsConstructor
@Slf4j
public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements AppUserService {

    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private final AppUserRoleService appUserRoleService;

    private final StringRedisTemplate redisTemplate;

    private final AppRoleService appRoleService;

    private final CacheManager cacheManager;

    /**
     * 更新用户
     *
     * @param userDTO
     * @return
     */
    @Override
    @CacheEvict(value = CacheConstants.USER_DETAILS_MINI, key = "#userDTO.username")
    public Boolean updateUser(AppUserDTO userDTO) {
        AppUser appUser = new AppUser();
        BeanUtils.copyProperties(userDTO, appUser);
        if (StrUtil.isNotBlank(userDTO.getPassword())) {
            appUser.setPassword(ENCODER.encode(userDTO.getPassword()));
        }
        this.updateById(appUser);

        appUserRoleService.remove(Wrappers.<AppUserRole>lambdaQuery().eq(AppUserRole::getUserId, appUser.getUserId()));
        userDTO.getRole().forEach(roleId -> {
            AppUserRole appUserRole = new AppUserRole();
            appUserRole.setRoleId(roleId);
            appUserRole.setUserId(userDTO.getUserId());
            appUserRole.insert();
        });
        return Boolean.TRUE;
    }

    /**
     * 新增用户
     *
     * @param userDTO
     * @return
     */
    @Override
    public Boolean saveUser(AppUserDTO userDTO) {
        AppUser appUser = new AppUser();
        BeanUtils.copyProperties(userDTO, appUser);
        appUser.setDelFlag(CommonConstants.STATUS_ENABLE);
        appUser.setPassword(ENCODER.encode(userDTO.getPassword()));
        baseMapper.insert(appUser);
        // 如果角色为空，赋默认角色
        if (CollUtil.isNotEmpty(userDTO.getRole())) {
            List<AppUserRole> userRoleList = userDTO.getRole().stream().map(roleId -> {
                AppUserRole userRole = new AppUserRole();
                userRole.setUserId(appUser.getUserId());
                userRole.setRoleId(roleId);
                return userRole;
            }).collect(Collectors.toList());
            appUserRoleService.saveBatch(userRoleList);
        }
        return Boolean.TRUE;
    }

    /**
     * 查询全部的用户
     *
     * @param appUser
     * @return
     */
    @Override
    public List<AppUserExcelVO> listUser(AppUserDTO appUser) {
        List<AppUser> appUsers = baseMapper.selectList(null);
        List<AppUserExcelVO> appUserExcelVOS = appUsers.stream().map(item -> {
            AppUserExcelVO appUserExcelVO = new AppUserExcelVO();
            BeanUtils.copyProperties(item, appUserExcelVO);
            return appUserExcelVO;
        }).collect(Collectors.toList());
        return appUserExcelVOS;
    }

    @Override
    public IPage getUsersWithRolePage(Page page, AppUserDTO appUserDTO) {
        return baseMapper.getUserVosPage(page, appUserDTO);
    }

    @Override
    public AppUserInfo findUserInfo(AppUser user) {
        AppUserInfo info = new AppUserInfo();
        info.setAppUser(user);
        // 设置角色列表 （ID）
        List<Long> roleIds = appRoleService.findRolesByUserId(user.getUserId())
                .stream()
                .map(AppRole::getRoleId)
                .collect(Collectors.toList());
        info.setRoles(ArrayUtil.toArray(roleIds, Long.class));

        // 设置权限列表（menu.permission）
        Set<String> permissions = new HashSet<>();
        info.setPermissions(ArrayUtil.toArray(permissions, String.class));
        return info;
    }

    @Override
    @CacheEvict(value = CacheConstants.USER_DETAILS_MINI, key = "#req.username")
    public R updateUserInfo(AppUserDTO req) {
        // C端客户修改手机号需要判断验证码是否正确
        // slothUser user = SecurityUtils.getUser();
        // if (UserTypeEnum.TOC.getStatus().equals(user.getUserType()) && StrUtil.isNotBlank(req.getPhone())) {
        //     String key = CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.APPSMS.getType() + StringPool.AT
        //             + req.getPhone();
        //     String codeObj = redisTemplate.opsForValue().get(key);
        //
        //     if (!req.getMobileCode().equals(codeObj)) {
        //         return R.failed(Hint.NON_VERIFY_CODE_ERROR);
        //     }
        // }

        AppUser appUser = baseMapper.selectById(req.getUserId());

        // 更新密码
        if (StrUtil.isNotBlank(req.getPassword())) {
            // 用户输入密码与数据库密码是否匹配
            if (!ENCODER.matches(PasswordUtil.decode(req.getPassword()), appUser.getPassword())) {
                return R.failed(Hint.NON_PASSWORD_ERROR);
            }
            // 校验密码
            checkPassword(req.getNewpassword1(), req.getConfirmPassword());
            req.setPassword(genEncodePassword(req.getPassword()));
        }

        BeanUtils.copyProperties(req, appUser);
        return R.ok(this.updateById(appUser));
    }

    @Override
    @CacheEvict(value = CacheConstants.USER_DETAILS_MINI, key = "#req.username")
    public R forgetPassword(ForgetPasswordDTO req) {
        AppUser appUser = baseMapper.selectOne(
                Wrappers.lambdaQuery(AppUser.class)
                        .eq(AppUser::getUsername, req.getUsername()),
                Boolean.FALSE
        );
        if (Objects.isNull(appUser)) {
            return R.failed(Hint.NON_ACCOUNT_NOT_EXIST);
        }

        // 校验密码
        checkPassword(req.getPassword(), req.getNewpassword1());

        return R.ok(
                update(
                        Wrappers.lambdaUpdate(AppUser.class)
                                .set(AppUser::getPassword, genEncodePassword(req.getPassword()))
                                .eq(AppUser::getUserId, appUser.getUserId())
                )
        );
    }

    @Override
    public AppUserVO selectUserVoById(Long userId) {
        return baseMapper.getUserVoById(userId);
    }

    /**
     * 删除user用户同时删除user-role关系表
     *
     * @param ids userIds
     */
    @Override
    public Boolean deleteAppUserByIds(Long[] ids) {
        Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS_MINI);
        for (AppUser appUser : baseMapper.selectBatchIds(CollUtil.toList(ids))) {
            cache.evict(appUser.getUsername());
        }
        // 删除用户关联表
        this.appUserRoleService
                .remove(Wrappers.<AppUserRole>lambdaQuery().in(AppUserRole::getUserId, CollUtil.toList(ids)));

        this.removeBatchByIds(CollUtil.toList(ids));

        return Boolean.TRUE;

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @RedisLock(name = RedisKeyCst.USER_REGISTER, key = "#req.username")
    public R registerAppUser(AppUserDTO req) {
        // 手机验证码登录
        if (Objects.nonNull(req.getLoginWay()) && BizCst.LoginWay.mobileAndValidCode == req.getLoginWay()) {
            return registerAppUserBySms(req);
        } else {
            return registerAppUserByAccount(req);
        }
    }

    @Override
    public Boolean addUserRole(AddUserRoleDTO req) {
        if (CollUtil.isNotEmpty(req.getRoleIdList())) {
            List<AppUserRole> userRoleList = req.getRoleIdList().stream().map(roleId -> {
                AppUserRole userRole = new AppUserRole();
                userRole.setUserId(req.getUserId());
                userRole.setRoleId(roleId);
                return userRole;
            }).collect(Collectors.toList());
            return appUserRoleService.saveBatch(userRoleList);
        } else {
            // 赋默认角色
            AppUserRole userRole = new AppUserRole();
            userRole.setUserId(req.getUserId());
            userRole.setRoleId(Long.valueOf(BizCst.User.Role.normal));
            return appUserRoleService.save(userRole);
        }
    }

    @Override
    public String genInvitationCode() {
        String code = ShareCodeUtil.genCode();
        LambdaQueryWrapper<AppUser> wrapper = Wrappers.lambdaQuery(AppUser.class)
                .eq(AppUser::getInvitationCode, code);
        while (count(wrapper) > 0) {
            code = ShareCodeUtil.genCode();
            wrapper.eq(AppUser::getInvitationCode, code);
        }

        return code;
    }

    /**
     * 手机验证码注册
     *
     * @param req
     * @return
     */
    private R registerAppUserBySms(AppUserDTO req) {
        long count = this.count(
                Wrappers.lambdaQuery(AppUser.class)
                        .eq(AppUser::getPhone, req.getPhone())
        );
        if (count > 0) {
            return R.failed(Hint.NON_ACCOUNT_EXIST);
        }

        String key = CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.APPSMS.getType() + StringPool.AT + req.getPhone();
        String codeObj = redisTemplate.opsForValue().get(key);

        if (!req.getMobileCode().equals(codeObj)) {
            return R.failed(Hint.NON_VERIFY_CODE_ERROR);
        }

        AppUser appUser = new AppUser();
        BeanUtils.copyProperties(req, appUser);
        appUser.setUsername(req.getPhone());
        appUser.setNickname(NameGenUtil.genRandomName());
        appUser.setDelFlag(CommonConstants.STATUS_ENABLE);
        appUser.setPassword(genEncodePassword(req.getPassword()));
        // 生成自己的邀请码
        appUser.setInvitationCode(this.genInvitationCode());
        // 处理邀请码
        handleInvitationCode(req, appUser);

        appUser.setRegisterIp(WebUtils.getIP());
        appUser.setRegisterTime(LocalDateTime.now());
        baseMapper.insert(appUser);

        // 添加用户角色
        addUserRole(
                AddUserRoleDTO.builder()
                        .userId(appUser.getUserId())
                        .roleIdList(req.getRole())
                        .build()
        );

        return R.ok();
    }

    /**
     * 处理邀请码
     * @param req
     * @param appUser
     */
    private void handleInvitationCode(AppUserDTO req, AppUser appUser) {
        // 存在邀请码
        if (StringUtil.isNotEmpty(req.getInvitationCode())) {
            // 根据邀请码查询用户
            AppUser invitationUser = this.getOne(
                    Wrappers.lambdaQuery(AppUser.class)
                            .eq(AppUser::getInvitationCode, req.getInvitationCode()),
                    Boolean.FALSE
            );
            // 邀请人存在
            if (Objects.nonNull(invitationUser)) {
                // 当前用户没有邀请人
                if (Objects.nonNull(appUser.getInviteId())) {
                    // 设置邀请人id
                    appUser.setInviteId(invitationUser.getUserId());
                    // appUser.setInviteTime(LocalDateTime.now());
                } else {
                    log.info(" 当前用户[{}]已有邀请人，不再设置邀请人", appUser.getUsername());
                }
            }
        }
    }


    /**
     * 账号密码注册
     *
     * @param req
     * @return
     */
    private R registerAppUserByAccount(AppUserDTO req) {

        // 校验密码
        checkPassword(req.getPassword(), req.getConfirmPassword());

        long count = this.count(
                Wrappers.lambdaQuery(AppUser.class)
                        .eq(AppUser::getUsername, req.getUsername())
        );
        if (count > 0) {
            return R.failed(Hint.NON_ACCOUNT_EXIST);
        }

        AppUser appUser = new AppUser();
        BeanUtils.copyProperties(req, appUser);
        appUser.setEmail(req.getUsername());
        appUser.setNickname(appUser.getEmail().substring(0, appUser.getEmail().indexOf(StrUtil.C_AT)));
        appUser.setDelFlag(CommonConstants.STATUS_ENABLE);
        appUser.setPassword(genEncodePassword(req.getPassword()));
        // 生成自己的邀请码
        appUser.setInvitationCode(this.genInvitationCode());
        // 处理邀请码
        handleInvitationCode(req, appUser);

        appUser.setRegisterIp(WebUtils.getIP());
        appUser.setRegisterTime(LocalDateTime.now());
        baseMapper.insert(appUser);

        // 添加用户角色
        addUserRole(
                AddUserRoleDTO.builder()
                        .userId(appUser.getUserId())
                        .roleIdList(req.getRole())
                        .build()
        );
        return R.ok();
    }


    /**
     * @param excelVOList
     * @param bindingResult
     * @return
     */
    @Override
    public R importUser(List<AppUserExcelVO> excelVOList, BindingResult bindingResult) {
        // 通用校验获取失败的数据
        List<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();

        // 执行数据插入操作 组装 UserDto
        for (AppUserExcelVO excel : excelVOList) {
            // 个性化校验逻辑
            List<AppUser> userList = this.list();
            List<AppRole> roleList = appRoleService.list();

            Set<String> errorMsg = new HashSet<>();
            // 校验用户名是否存在
            boolean exsitUserName = userList.stream()
                    .anyMatch(sysUser -> excel.getUsername().equals(sysUser.getUsername()));

            if (exsitUserName) {
                errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERNAME_EXISTING, excel.getUsername()));
            }

            // 判断输入的角色名称列表是否合法
            List<String> roleNameList = StrUtil.split(excel.getRoleNameList(), StrUtil.COMMA);
            List<AppRole> roleCollList = roleList.stream()
                    .filter(role -> roleNameList.stream().anyMatch(name -> role.getRoleName().equals(name)))
                    .collect(Collectors.toList());

            if (roleCollList.size() != roleNameList.size()) {
                errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_ROLE_ROLENAME_INEXISTENCE, excel.getRoleNameList()));
            }
            // 数据合法情况
            if (CollUtil.isEmpty(errorMsg)) {
                insertExcelUser(excel, roleCollList);
            } else {
                // 数据不合法情况
                errorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg));
            }

        }

        if (CollUtil.isNotEmpty(errorMessageList)) {
            return R.failed(errorMessageList);
        }
        return R.ok(Optional.ofNullable(null), MsgUtils.getMessage(ErrorCodes.SYS_USER_IMPORT_SUCCEED));

    }

    private void insertExcelUser(AppUserExcelVO excel, List<AppRole> roleCollList) {
        AppUserDTO userDTO = new AppUserDTO();
        userDTO.setUsername(excel.getUsername());
        userDTO.setPhone(excel.getPhone());
        userDTO.setNickname(excel.getNickname());
        userDTO.setName(excel.getName());
        userDTO.setEmail(excel.getEmail());
        // 批量导入初始密码为手机号
        userDTO.setPassword(userDTO.getPhone());
        // 根据角色名称查询角色ID
        List<Long> roleIdList = roleCollList.stream().map(AppRole::getRoleId).collect(Collectors.toList());
        userDTO.setRole(roleIdList);
        // 插入用户
        this.saveUser(userDTO);
    }

    /**
     * 检查密码
     *
     * @param password  加密后的密码
     * @param password2 加密后的密码
     */
    private void checkPassword(String password, String password2) {
        if (StrUtil.isBlank(password) || StrUtil.isBlank(password2)) {
            throw new IllegalArgumentException("password is not null");
        }
        // 两次密码是否一致
        if (!StrUtil.equals(password, password2)) {
            throw new BizException(Hint.NON_PASSWORD_DIFFERENT);
        }

        // 原文密码
        String decodePassword = PasswordUtil.decode(password);

        // 密码格式是否正确
        if (!ReUtil.isMatch(RegCst.PASSWORD, decodePassword)) {
            throw new BizException(Hint.NON_PASSWORD_FORMAT_ERROR);
        }

    }


    /**
     * 生成加密密码
     *
     * @param password 前端传入的密码
     * @return
     */
    private String genEncodePassword(String password) {
        // 先解密前端传入的密码再进行加密
        String decode = PasswordUtil.decode(password);
        return ENCODER.encode(decode);
    }
}
